/**
 * 
 */
package biz.innovasoft.nodb.impl;

import java.io.Serializable;
import java.util.List;

import org.prevayler.Prevayler;

import biz.innovasoft.nodb.RepositoryException;
import biz.innovasoft.nodb.UpdateableList;
import biz.innovasoft.nodb.index.IndexException;
import biz.innovasoft.nodb.index.IndexManager;
import biz.innovasoft.nodb.index.Indexer;
import biz.innovasoft.nodb.index.RepositoryIndexManager;
import biz.innovasoft.nodb.index.Searcher;
import biz.innovasoft.nodb.index.SearcherResult;
import biz.innovasoft.nodb.query.QueryCriteria;
import biz.innovasoft.nodb.query.Result;
import biz.innovasoft.nodb.query.Tuple;
import biz.innovasoft.nodb.util.ArrayUpdateableList;
import biz.innovasoft.nodb.util.IndexUtils;

/**
 * 
 * @author Diego Miranda
 *
 */
public class FindHelper implements Serializable {

	private static final long serialVersionUID = 1102956905193518896L;
	
	private GenericRepository repository;
	private Searcher searcher;
	private RepositoryIndexManager temporal = new RepositoryIndexManager();
		
	private Prevayler prevayler;
	private QueryCriteria criteria;
	
	private IndexManager index;
		
	public FindHelper (GenericRepository repository) {
		this.repository = repository;
	}
	
	public FindHelper (GenericRepository repository, IndexManager index, boolean stricted) {
		this.repository = repository;
		this.index = index;
		if (stricted) {
			searcher = new Searcher (index, true);
		} else {
			searcher = new Searcher (index);
		}
	}
	
	public FindHelper (Prevayler prevayler, GenericRepository repository, QueryCriteria criteria) {
		this.prevayler = prevayler;
		this.repository = repository;
		this.criteria = criteria;
		this.index = repository.getIndexManager(criteria.getCriteriaClass());
		if (criteria.isStricted()) {
			searcher = new Searcher (index, true);
		} else {
			searcher = new Searcher (index);
		}
	}
	
	@SuppressWarnings("unchecked")
	public Result find () {
		if (criteria.getExpressions().size() == 0) {
			return findInMaster(criteria, true);
		}
		return find (criteria, true);
	}
	
	@SuppressWarnings("unchecked")
	public Result find(QueryCriteria query, boolean copy) {
		
		ResultImpl result = new ResultImpl(prevayler);
		result.setMaxResults(query.getMaxResults());
		result.setPage(query.getPage()+1);
		
		SearcherResult sresult = searcher.searchObject(query);
		if (sresult.size() > 0) {
			result.setTotalHits(sresult.getTotalHits());
			result.setMaxRange(sresult.getMaxRange());
			result.setMinRange(sresult.getMinRange());
			result.setTuples(repository.getObjectsByOId(sresult.getOids(), true));
			return result;
		}
			
		if (sresult.size() == 0 && !canQueryRunWithIndex(query)) {
			if (repository.getConfiguration().getUseTemporalIndex()) {
				return createIndexAndFind(query, copy);
			}	
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public Result findInMaster(QueryCriteria query, boolean copy) {
		
		ResultImpl result = new ResultImpl(prevayler);
		result.setMaxResults(query.getMaxResults());
		result.setPage(query.getPage()+1);
		
		SearcherResult sresult = searcher.searchObjectInMaster(query);
		if (sresult.size() > 0) {
			result.setTotalHits(sresult.getTotalHits());
			result.setMaxRange(sresult.getMaxRange());
			result.setMinRange(sresult.getMinRange());
			result.setTuples(repository.getObjectsByOId(sresult.getOids(), true));
			return result;
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	public Result createIndexAndFind(
			QueryCriteria query, boolean copy) {

		String qid = query.getQueryCacheId();
		String[] indexesPath = IndexUtils.getExpressionAttributesAsArray(query
				.getExpressions());
		IndexManager imanager = temporal.createIndexAndPath(query.getCriteriaClass());
		if (!imanager.hasIndexEntries()) {
			temporal.createIndex(query.getCriteriaClass(), indexesPath);
			reindex(imanager);
		}
		
		if (imanager != null) {
			
			Searcher searcher = new Searcher(imanager);
			SearcherResult sresult = searcher.searchObject(query);
			if (sresult.size() > 0) {
				ResultImpl result = new ResultImpl (true, qid);
				result.setMaxResults(query.getMaxResults());
				result.setPage(query.getPage()+1);
				result.setTotalHits(sresult.getTotalHits());
				result.setMaxRange(sresult.getMaxRange());
				result.setMinRange(sresult.getMinRange());
				result.setTuples(repository.getObjectsByOId(sresult.getOids(), true));
				return result;
			}
			
		}
		return new ResultImpl (true, null);
	}
	
	private boolean canQueryRunWithIndex(QueryCriteria criteria) {
		List<String> criteriaAttrs = IndexUtils
				.getExpressionAttributes(criteria.getExpressions());
		if (criteriaAttrs.size() == 0)
			return false;
		for (String attr : criteriaAttrs) {
			if (!index.containsEntry(attr)) {
				return false;
			}
		}
		return true;
	}
	
	public void reindex(Class<?> clazz) throws RepositoryException {
		IndexManager imanager = temporal.getIndexManager(clazz);
		Indexer indexer = null;
		try {
			indexer = new Indexer(imanager, true, true);
			List<Tuple> tuples = repository.getAll(clazz, true);
			for (Tuple tuple : tuples) {
				indexer.indexObject(tuple.oid, tuple.value);
			}
		} catch (IndexException e) {
			throw new RepositoryException(e);
		} finally {
			if (indexer != null)
				indexer.close();
		}
	}

	protected void reindex(IndexManager imanager) throws RepositoryException {
		Indexer indexer = null;
		try {
			indexer = new Indexer(imanager, true, true);
			List<Tuple>tuples = repository.getAll(imanager.getIndexClass(), true);
			for (Tuple tuple : tuples) {
				indexer.indexObject(tuple.oid, tuple.value);
			}
		} catch (IndexException e) {
			throw new RepositoryException(e);
		} finally {
			if (indexer != null)
				indexer.close();
		}
	}
	
	private class ResultImpl implements Result {
		
		private static final long serialVersionUID = 1L;
		
		private boolean useTemporalIndex;
		private String indexCachedId;
		
		private int totalHits;
		private int page;
		private int maxResults;
		private int minRange;
		private int maxRange;
		private List<Tuple> tuples;
		private Prevayler prevayler;
		
		public ResultImpl ( Prevayler prevayler ) {
			this.prevayler = prevayler;
		}
		
		public ResultImpl (boolean useTemporal, String indexCachedId) {
			super();
			this.useTemporalIndex = useTemporal;
		}
		
		public boolean getUseTemporalIndex () {
			return useTemporalIndex;
		}
		
		public String getIndexCacheId() {
			return indexCachedId;
		}

		public int getTotalHits() {
			return totalHits;
		}

		public void setTotalHits(int totalHits) {
			this.totalHits = totalHits;
		}

		public int getPage() {
			return page;
		}

		public void setPage(int page) {
			this.page = page;
		}

		public int getMaxResults() {
			return maxResults;
		}

		public void setMaxResults(int maxResults) {
			this.maxResults = maxResults;
		}

		public int getMinRange() {
			return minRange;
		}

		public void setMinRange(int minRange) {
			this.minRange = minRange;
		}

		public int getMaxRange() {
			return maxRange;
		}

		public void setMaxRange(int maxRange) {
			this.maxRange = maxRange;
		}
		
		public int getMaxPages () {
			if (totalHits > 0 && maxResults > 0) {
				if (totalHits > maxResults) {
					return (int)Math.floor ( (totalHits/maxResults) );
				}
			}
			return 0;
		}
		
		protected void setTuples (List<Tuple> tuples) {
			this.tuples = tuples;
		}
		
		public int size() {
			if (tuples == null) {
				return 0;
			}
			return tuples.size();
		}

		@SuppressWarnings("unchecked")
		@Override
		public <R>UpdateableList<R> asUpdateableList() {
			UpdateableList list = new ArrayUpdateableList(prevayler, tuples);
			return list;
		}

		@Override
		public List<Tuple> asListOfTuples() {
			return tuples;
		}
	}
	
}
